﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using Win32.WtsApi32;
using System.Threading;
using System.ComponentModel;
using RDPAddins.Common;

namespace RDPAddins
{
    internal class Channel: IChannel, IUI
    {
        uint openChannel = 0;
        ChannelOpenEventDelegate channelOpenEventDelegate;
        ManualResetEvent write = new ManualResetEvent(true);
        internal object tooltipconext = null;
        Queue<MemoryChunk> buffers = new Queue<MemoryChunk>();
        object readlock = new object();

        public event EventHandler Connected;
        public event EventHandler Disconnected;
        public event EventHandler Terminated;
        public event DataArrivedHandler DataArrived;
        public event BalloonTipClickedHandler BalloonTipClicked;
        public Lazy<IAddin, IAddinMetadata> Addin;

        public Channel(RDPClient client, Lazy<IAddin, IAddinMetadata> addin)
        {
            Client = client;
            Addin = addin;
            Addin.Value.Initialize(this);
            ChannelDef = new ChannelDef(){name = Metadata.ChannelName, options = Metadata.ChannelOptions };
            channelOpenEventDelegate = new ChannelOpenEventDelegate(VirtualChannelOpenEvent);
        }

        internal ChannelDef ChannelDef;

        public IChannel Parent { get { return this; } }

        public bool Visible
        {
            get
            {
                if (Page != null)
                    return TabCtrl.TabPages.Contains(Page);
                return false;
            }
            set
            {
                if (Page != null)
                {
                    if (value)
                    {
                        if (!TabCtrl.TabPages.Contains(Page))
                            TabCtrl.TabPages.Add(Page);

                    }
                    else
                    {
                        if (TabCtrl.TabPages.Contains(Page))
                            TabCtrl.TabPages.Remove(Page);
                    }
                }
            }
        }

        private void VirtualChannelOpenEvent(uint openHandle, ChannelEvents openEvent, byte[] data, uint dataLength, uint totalLength, ChannelFlags dataFlags)
        {
            switch (openEvent)
            {
                case ChannelEvents.DataRecived:
                    int flag = (int)(dataFlags & ChannelFlags.Only);
                    WriteCore(data, dataLength);
                    OnDataArrived(dataLength, totalLength, (DataParts)flag);
                    break;
                case ChannelEvents.WriteComplete:
                    write.Set();
                    break;
            }
        }

        void WriteCore(byte[] data, uint count)
        {
            var mem = AllocateMemoryChunk(count);
            Buffer.BlockCopy(data, 0, mem.Buffer, 0, (int)count);
            lock (readlock)
            {
                buffers.Enqueue(mem);
                AvailableDataLength += count;
            }
        }

        

        public void Write(byte[] data, int offset, int count)
        {
            byte[] buff = data;
            if (offset != 0)
            {
                buff = new byte[count];
                Buffer.BlockCopy(data, offset, buff, 0, count);
            }
            write.Reset();
            ChannelReturnCodes ret = Client.EntryPoint.VirtualChannelWrite(openChannel, buff, (uint)count, IntPtr.Zero);
            write.WaitOne();
        }


        public int Read(byte[] buffer, int offset, int count)
        {
            if (AvailableDataLength > 0)
            {
                int toread = 0;
                lock (readlock)
                {

                    if (buffers.Count != 0)
                    {
                        var buf = buffers.Peek();
                        toread = buf.Buffer.Length - buf.Offset;
                        int off = buf.Offset;
                        if (count >= toread)
                            buffers.Dequeue();
                        else
                        {
                            toread = count;
                            buf.Offset += toread;
                        }
                        Buffer.BlockCopy(buf.Buffer, off, buffer, offset, toread);
                        AvailableDataLength -= (uint)toread;
                    }
                    return toread;
                }
            }
            return 0;
        }


        TabPage Page = null;
        TabControl TabCtrl = null;
        RDPClient Client = null;


        public uint AvailableDataLength { get; private set; }


        internal ChannelReturnCodes ConnectedInternal(IntPtr Handle)
        {
            ChannelReturnCodes ret = Client.EntryPoint.VirtualChannelOpen(Handle, ref openChannel, Addin.Metadata.ChannelName, channelOpenEventDelegate);
            if (ret == ChannelReturnCodes.Ok)
                OnConnected(this, new EventArgs());
            return ret;
        }

        internal void DisconnectedInternal()
        {
            openChannel = 0;
            OnDisconnected(this, new EventArgs());
            if (Stream != null)
            {
                Stream.Dispose();
                Stream = null;
            }
        }

        protected virtual void OnConnected(object sender, EventArgs e)
        {
            if (Connected != null)
                DoOnUIThread(() => Connected(sender, e));
        }

        protected virtual void OnDisconnected(object sender, EventArgs e)
        {
            if (Disconnected != null)
                DoOnUIThread(() => Disconnected(sender, e));
        }

        protected virtual void OnTerminated(object sender, EventArgs e)
        {
            if (Terminated != null)
                DoOnUIThread(() => Terminated(sender, e));
        }

        internal void TerminatedInternal()
        {
            OnTerminated(this, new EventArgs());
        }

        protected virtual void OnInitialized(object sender, EventArgs e)
        {
            if (Initialized != null)
                DoOnUIThread(() => Initialized(sender, e));
        }

        internal void InitializedInternal()
        {
            OnInitialized(this, new EventArgs());
        }

        public void ShowControl()
        {
            if (Page != null)
            {
                Client.MainForm.Visible = true;
                Client.MainForm.Tab.SelectTab(Page);
            }
        }

        public void DoOnUIThread(Action action)
        {
            if (Client.MainForm.InvokeRequired)
                Client.MainForm.Invoke(action);
            else
                action();
        }

        public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon, object context)
        {
            tooltipconext = context;
            Client.MainForm.ShowToolTip(timeout, tipTitle, tipText, tipIcon, this);
        }

        protected virtual void OnDataArrived(uint dataLength, uint totalLength, DataParts dataFlags)
        {
            if (DataArrived != null)
                DoOnUIThread(() => DataArrived(dataLength, totalLength, dataFlags));
        }

        internal protected virtual void OnBalloonTipClicked(object sender, EventArgs e)
        {
            if (BalloonTipClicked != null)
                DoOnUIThread(() => BalloonTipClicked(this, tooltipconext));
        }

        private MemoryChunk AllocateMemoryChunk(uint newSize)
        {
            MemoryChunk chunk = new MemoryChunk(newSize);
            return chunk;
        }

        ChannelStream Stream = null;

        private class MemoryChunk
        {
            internal byte[] Buffer;
            internal int Offset = 0;
            internal MemoryChunk(uint bufferSize)
            {
                Buffer = new byte[bufferSize];
                Offset = 0;
            }
        }


        public event EventHandler Initialized;

        public Stream GetStream()
        {
            Stream = Stream ?? new ChannelStream(this);
            return Stream;
        }

        public IAddinMetadata Metadata { get { return Addin.Metadata; } }

        public IUI UI { get { return this; } }


        public event ControlCreatingHandler ControlCreating;
        public event MenuCreatingHandler MenuCreating;
        public event MenuCreatingHandler TrayMenuCreating;
        public event IconCreatingHandler IconCreating;

        internal void CreateUI()
        {
            if (ControlCreating != null)
            {
                var control = ControlCreating(this);
                if (control != null)
                {
                    Page = new TabPage(Metadata.AddinName);
                    TabCtrl = Page.Parent as TabControl;
                    control.Dock = DockStyle.Fill;
                    Page.Controls.Add(control);
                    Client.MainForm.Tab.TabPages.Add(Page);
                    Image icon = null;
                    if (IconCreating != null)
                        icon = IconCreating(this);
                    Page.ImageIndex = Client.MainForm.AddImage(icon);
                }
            }
            if (MenuCreating != null)
            {
                var menu = MenuCreating(this);
                if (menu != null)
                {
                    var parentmenu = new MenuItem(Metadata.AddinName);
                    parentmenu.MenuItems.AddRange(menu.MenuItems.Cast<MenuItem>().ToArray());
                    Client.MainForm.MenuAddins.MenuItems.Add(parentmenu);
                }
            }
            if (TrayMenuCreating != null)
            {
                var menu = TrayMenuCreating(this);
                if (menu != null)
                {
                    var parentmenu = new MenuItem(Metadata.AddinName);
                    parentmenu.MenuItems.AddRange(menu.MenuItems.Cast<MenuItem>().ToArray());
                    Client.MainForm.mnuAddinsContext.MenuItems.Add(parentmenu);
                }
            }
        }
    }
}

